package com.ilikesoup.instant.enums.basic;

import org.springframework.util.ConcurrentReferenceHashMap;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 基于编码的枚举
 * 提供通过编码获取枚举项的能力
 *
 * @param <K> 枚举项code的类型
 * @author Soup
 */
public interface CodeEnum<K> {
    /**
     * @return 枚举项的编码，必须唯一，不允许为null
     */
    K getCode();

    /**
     * 判断编码是否相同，简化了 {@code xxx.getCode().equals(code)} 的写法，
     * 并且通过泛型了两个code的类型，避免出现 {@code string.equals(integer)}的错误
     */
    default boolean codeEquals(K code) {
        return Objects.equals(getCode(), code);
    }

    /**
     * 通过枚举项编码获取枚举项
     *
     * @param code          枚举项编码
     * @param codeEnumClass 枚举类
     * @param <T>           枚举类型
     * @param <K>           编码类型
     * @return 对应编码的枚举项
     */
    static <T extends Enum<T> & CodeEnum<K>, K> T getByCode(K code, Class<T> codeEnumClass) {
        if(CodeEnumCache.USE_CACHE) {
            return CodeEnumCache.getCodeEnumMap(codeEnumClass).get(code);
        }
        for (T constant : codeEnumClass.getEnumConstants()) {
            if(constant.codeEquals(code)) {
                return constant;
            }
        }
        return null;
    }

    /**
     * 通过枚举项编码获取枚举项, 如果不存在就返回默认值
     *
     * @param code          枚举项编码
     * @param codeEnumClass 枚举类
     * @param def           默认值，如果找不到匹配的项，就返回该默认值
     * @param <T>           枚举类型
     * @param <K>           编码类型
     * @return              返回对应编码的枚举项，如果没有找到返回默认的枚举项
     */
    static <T extends Enum<T> & CodeEnum<K>, K> T getByCode(K code, Class<T> codeEnumClass, T def) {
        return Optional.ofNullable(getByCode(code, codeEnumClass)).orElse(def);
    }

    /**
     * 判断编码在枚举中是否存在
     *
     * @param code          枚举项编码
     * @param codeEnumClass 枚举类
     * @param <T>           枚举类型
     * @param <K>           编码类型
     * @return 存在返回true, 不存在返回false
     */
    static <T extends Enum<T> & CodeEnum<K>, K> boolean contains(K code, Class<T> codeEnumClass) {
        return getByCode(code, codeEnumClass) != null;
    }

    /**
     * 兼容没有实现 CodeEnum 的枚举
     *
     * @param code       编码
     * @param codeGetter 获取与编码比较值的方法
     * @param enumClass  枚举类
     * @param <T>        只适用于枚举类
     * @param <K>        编码类型
     * @return 对应编码的枚举项
     */
    static <T extends Enum<T>, K> T getByCode(K code, Function<T, K> codeGetter, Class<T> enumClass) {
        T[] constants = enumClass.getEnumConstants();
        for (T constant : constants) {
            if (Objects.equals(codeGetter.apply(constant), code)) {
                return constant;
            }
        }
        return null;
    }

    /**
     * CodeEnum是否包含某个编码
     * 方便非静态Class对象调用，无法强转为复合泛型Class
     * @param code              编码
     * @param codeEnumClass     编码枚举类
     * @see #contains(Object, Class)
     * @throws IllegalArgumentException 当枚举类不为CodeEnum的实现类时，会报错参数不合法异常
     */
    static boolean containsGeneric(Object code, Class<? extends Enum<?>> codeEnumClass) {
        if (CodeEnum.class.isAssignableFrom(codeEnumClass)) {
            return CodeEnumCache.getCodeEnumMapGeneric(codeEnumClass).containsKey(code);
        }
        throw new IllegalArgumentException("Class is not an implementation of CodeEnum :" + codeEnumClass);
    }

    /**
     * 通过键查找CodeEnum
     * 方便非静态Class对象调用，无法强转为复合泛型Class
     * @param code              编码
     * @param codeEnumClass     编码枚举类
     * @see #getByCode(Object, Class)
     * @throws IllegalArgumentException 当枚举类不为CodeEnum的实现类时，会报错参数不合法异常
     */
    @SuppressWarnings("unchecked")
    static <K> CodeEnum<K> getByCodeGeneric(K code, Class<? extends Enum<?>> codeEnumClass) {
        if (CodeEnum.class.isAssignableFrom(codeEnumClass)) {
            return (CodeEnum<K>) CodeEnumCache.getCodeEnumMapGeneric(codeEnumClass).get(code);
        }
        throw new IllegalArgumentException("Class is not an implementation of CodeEnum :" + codeEnumClass);
    }

    /**
     * 编码枚举缓存，提高通过编码查找枚举的效率
     */
    @SuppressWarnings("unchecked")
    class CodeEnumCache {
        /**
         * 使用Spring库中的实现，如果不想使用，就用 {@code Collections.synchronizedMap(new WeakHashMap<>()) } 替代。
         */
        private static final Map<Class<? extends Enum<?>>, Map<?, ?>> ENUM_MAP_CACHE = new ConcurrentReferenceHashMap<>();
        /**
         * 缓存开关，默认开启
         */
        private static final boolean USE_CACHE = Optional.ofNullable(System.getProperty("CODE_ENUM_CACHE"))
                .map(val -> !"false".equalsIgnoreCase(val)).orElse(Boolean.TRUE);

        /**
         * 声明为私有方法，仅在 CodeEnum中使用，防止暴露出去被用户污染
         */

        private static <T extends Enum<T> & CodeEnum<K>, K> Map<K, T> getCodeEnumMap(Class<T> codeEnumClass) {
            return (Map<K, T>) ENUM_MAP_CACHE.computeIfAbsent(codeEnumClass,
                    k -> Arrays.<CodeEnum<K>>stream(codeEnumClass.getEnumConstants())
                            .collect(Collectors.toMap(CodeEnum::getCode, Function.identity(), (a, b) -> a)));
        }

        private static Map<?, ?> getCodeEnumMapGeneric(Class<? extends Enum<?>> codeEnumClass) {
            return ENUM_MAP_CACHE.computeIfAbsent(codeEnumClass,
                    k -> Arrays.stream(codeEnumClass.getEnumConstants())
                            .collect(Collectors.toMap(o -> ((CodeEnum<?>) o).getCode(), Function.identity(), (a, b) -> a)));
        }

    }

}
